import Konva from "konva";
import PptxGenJS from "pptxgenjs";
import { parse } from "pptxtojson";
import { Draw } from "../Draw/Draw";
import { IKeyMap } from "../Interface/Pptx";
import { IKonva } from "../Interface/IKonva";
import { KONVA_GRAPH_NAME } from "../Config";
import { PreviewMode } from "../Enumerate/PreviewMode";
import { BaseElement, TPptJson } from "../Interface/pptxToJson";
import { latexTemp, richTextTemp } from "../Template/DialogTemp";
import { setStageCursor } from "../Event/handle/konva/stage-cursor";
import { IBackgroundLayerOptions, VoidFun } from "../Interface/Pptx";
import { getColorMatch, getUniqueId, writeClipboard } from "../Utils";
import { setLineSelected } from "../Event/handle/konva/line-selected";
import { color_getHexColor, updateShapeOptions } from "../Utils/konva";
import { isEmpty, isPreview, nextTick, parsePptxText } from "../Utils";
import { tableTemp, echartTemp, slideTemp } from "../Template/DialogTemp";
import { setGroupTransformer } from "../Event/handle/konva/group-transformer";

export class CommandAdapt {
  private draw: Draw;

  constructor(draw: Draw) {
    this.draw = draw;
  }

  /**
   * 上一个幻灯片
   */
  public prevSlide = () => this.draw.getLayerManager().prevSlide();

  /**
   * 下一个幻灯片
   */
  public nextSlide = () => this.draw.getLayerManager().nextSlide();

  /**
   * 切换指定幻灯片
   * @param index 幻灯片索引 1 2 3 ...
   */
  public switchSlide(index: number) {
    this.draw.getLayerManager().switchLayer(index - 1);
  }

  /**
   * 从头开始预览
   */
  public previewBeginning() {
    const previewManager = this.draw.getPreviewManager();
    previewManager.preview(PreviewMode.beginning);
  }

  /**
   * 从当前幻灯片开始预览
   */
  public previewCurrent() {
    const previewManager = this.draw.getPreviewManager();
    previewManager.preview(PreviewMode.current);
  }

  /**
   * 指定幻灯片预览
   * @param index 自然顺序 1 2 3 ...
   */
  public previewSlide(index: number) {
    const previewManager = this.draw.getPreviewManager();
    previewManager.preview(PreviewMode.index, index - 1);
  }

  /**
   * 添加新幻灯片
   */
  public addSlide = () => this.draw.addSlide();

  /**
   * 获取幻灯片缩略图
   */
  public getSlideImage = () => this.draw.getLayerManager().getLayerImage();

  /**
   * 屏幕放映录制
   */
  public record() {
    // 检查MediaRecorder是否可用
    if (!MediaRecorder.isTypeSupported("video/webm")) {
      return this.draw.getMessage().warn("当前浏览器不支持屏幕录制.");
    }

    // 获取屏幕媒体流
    navigator.mediaDevices
      .getDisplayMedia({ video: true, audio: true })
      .then((stream) => {
        // 处理获取到的媒体流
        const recorder = new MediaRecorder(stream, {
          mimeType: "video/webm",
        });
        const root = this.draw.getRootBox();

        // 监听录制数据事件
        recorder.ondataavailable = (event) => {
          // 处理录制的数据
          const blob = new Blob([event.data], { type: "video/webm" });
          const url = URL.createObjectURL(blob);
          const a = document.createElement("a");
          a.href = url;
          // 取文件名
          const selector = ".konva-root-topmenu-filename";
          const filename = <HTMLElement>root.querySelector(selector);
          a.download = `UniPPTX_${filename?.innerText}_${Date.now()}.webm`;
          a.click();
          URL.revokeObjectURL(url);
        };

        // 开始录制
        recorder.start();
        // 在需要停止录制时调用
        // recorder.stop();
      })
      .catch((error) => console.error("Error capturing screen:", error));
  }

  /**
   * 导入PPTX
   */
  public importPptx() {
    // 在线DEMO： https://pipipi-pikachu.github.io/pptxtojson/
    // 类型参考： https://github.com/pipipi-pikachu/pptxtojson/blob/master/dist/index.d.ts

    const message = this.draw.getMessage();

    // 创建 input 接受 pptx  file
    const input = document.createElement("input");
    input.type = "file";
    input.accept =
      "application/vnd.openxmlformats-officedocument.presentationml.presentation";

    // 输入框事件
    const inputChange = () => {
      message.warn("文件导入为 Bate 测试版，部分幻灯片显示可能存在异常");

      const file = input.files?.[0];
      if (!file) return;

      // 读取文件内容为二进制数据
      const reader = new FileReader();
      reader.readAsArrayBuffer(file);

      reader.onload = async (e) => {
        input.remove();
        const data = <ArrayBuffer>e.target!.result;
        if (!data) return;
        const json = await parse(data);
        if (!json) return;
        this.parsePptJson(json);
      };
    };

    input.addEventListener("change", inputChange);

    input.click();
  }

  /**
   * 执行真正的文件识别，通过json创建本应用幻灯片
   * @param json pptxtojson 识别的 幻灯片json数据
   */
  private parsePptJson(json: TPptJson) {
    console.log(json);

    const layerManager = this.draw.getLayerManager();
    const konvaGraph = this.draw.getKonvaGraph();

    // 1. 先解决幻灯片宽高不一致问题
    const { width, height } = json.size;
    const stage = this.draw.getStage();
    stage.size({ width, height });

    // 2. 清空所有layer
    this.draw.clearLayer();
    this.draw.setLayer(null);
    layerManager.clearLayerList();
    this.draw.render();

    // 3. 根据 json.slides 创建幻灯片
    json.slides.forEach((slide) => {
      // 每一项是一个幻灯片
      const layer = new Konva.Layer({ id: getUniqueId() });
      this.draw.clearLayer(); // 清空 layer

      // 添加到 stage
      this.draw.addSlideToEditor(layer);

      // 更新背景 - 仅针对 fill 纯色填充类型 后面的类型后面再添加
      if (slide.fill.type === "color" && slide.fill.value) {
        layerManager.setBackgroundLayer({ fill: slide.fill.value });
      }

      // 针对具体类别映射为 konvaGraph 类型，并添加到 layer 上，重新渲染
      if (slide.elements.length) {
        slide.elements.forEach((ele) => {
          // borderColor borderType borderWidth fillColor height left name rotate *shapType="rect"* top type="shape" width
          const { height, width, left, top } = ele;
          const {
            borderColor,
            borderWidth,
            rotate,
            shapType,
            fillColor,
            content,
          } = <BaseElement>ele;

          let shape = <Konva.Group | null>null;

          // 还需要识别文本 - pptxtojson 识别的文本是 html 格式 需要转成 普通文本 + 样式
          const { text, fontFamily, fontSize, color } = parsePptxText(content);

          switch (shapType) {
            // 矩形
            case "rect":
              shape = konvaGraph.Rect({
                x: left,
                y: top,
                width,
                text,
                text_fill: color,
                text_fontFamily: fontFamily,
                text_fontSize: fontSize,
                height,
                fill: fillColor,
                stroke: borderColor,
                strokeWidth: borderWidth,
                rotation: rotate,
              });
              break;

            // 圆角矩形
            case "roundRect":
              shape = konvaGraph.Rect({
                x: left,
                y: top,
                width,
                text,
                text_fill: color,
                text_fontFamily: fontFamily,
                text_fontSize: fontSize,
                height,
                fill: fillColor,
                stroke: borderColor,
                strokeWidth: borderWidth,
                cornerRadius: [10, 10, 10, 10],
                rotation: rotate,
              });
              break;

            // 椭圆 - 直接取宽高的一半即可
            case "ellipse":
              shape = konvaGraph.Ellipse({
                x: left,
                y: top,
                width,
                height,
                text,
                text_fill: color,
                text_fontFamily: fontFamily,
                text_fontSize: fontSize,
                radiusX: width / 2,
                radiusY: height / 2,
                fill: fillColor,
                stroke: borderColor,
                strokeWidth: borderWidth,
                rotation: rotate,
              });
              break;

            default:
              break;
          }

          if (shape) {
            // 添加到 layer 上
            konvaGraph.addShape({ shape });
          }
        });
      }

      this.draw.render();
    });
  }

  /**
   * 导出 PPTX - pptxgenjs
   */
  public exportPptx() {
    const message = this.draw.getMessage();
    const root = this.draw.getRootBox();
    const stage = this.draw.getStage();
    const stageWidth = stage.width();
    const stageHeight = stage.height();
    const layerlistData = this.draw.getLayerManager().getLayerData();

    message.warn("文件导出为 Bate 测试版，部分幻灯片显示可能存在异常");

    const pptx = new PptxGenJS();

    /**
     * 重要知识：
     *  1. unipptx 是16/9 宽高比，因此此处直接设置未 LAYOUT_16x9 即可
     *  2. 但是 x y w h 不能直接设置为 100 200 ，因为 number 类型仅识别为 0-n
     *  3. 因此 需要转换成 100% ，需要先获取当前的画布宽高，再计算 百分比
     *  4. 更多配置项请查阅 https://gitbrent.github.io/PptxGenJS/docs/quick-start/
     */

    pptx.defineLayout({ name: "UP16*9", width: 16, height: 9 });
    pptx.layout = "UP16*9";

    /** 工具函数 - 转换成 百分比 显示 */
    function getPercent(type: "w" | "h" | "x" | "y", v: number) {
      let result = null;
      if (type === "h" || type === "y") result = (v / stageHeight) * 100 + "%";
      else result = (v / stageWidth) * 100 + "%";
      return <PptxGenJS.Coord>result;
    }

    const getColor = (f: string) => color_getHexColor(f);

    // 根据当前幻灯片列表，创建幻灯片
    layerlistData.forEach((layer) => {
      const data = JSON.parse(layer);
      const layerList = <Konva.Group[]>data.children;
      const slide = pptx.addSlide();

      // 内层循环是创建幻灯片元素 - shape image table echarts ...
      layerList.forEach((group) => {
        // 解析可能使用到的参数变量
        const { width, height } = group.attrs;
        const { name, shapeType, data } = group.attrs;
        const { rotate, realHeight, realWidth } = group.attrs;
        const fill =
          group.children[0].attrs.fill ||
          group.attrs.fill ||
          getColorMatch(this.draw, "nodeFillColor");

        // 设置当前幻灯片背景
        if (name === "bgGroup") {
          // 设置当前背景
          slide.background = { color: fill };
        }

        // 识别基础元素
        if (name === KONVA_GRAPH_NAME) {
          // 统一处理位置宽高信息
          const x = getPercent("x", group.attrs.x);
          const y = getPercent("y", group.attrs.y);
          const w = getPercent("w", realWidth || width);
          const h = getPercent("h", realHeight || height);
          const baseInfo = { x, y, w, h };
          const textNode = group.children[1];

          // 基础图形-这里还需要识别 文本、图片、表格、统计图、多媒体...
          switch (shapeType) {
            // 图形 addShape
            case "Rect":
              // ** 缩放后 宽高不变 变得是缩放比例，这里也需要处理
              // ** group fill 不稳定，应该直接取 group.findOne('Rect').attrs.fill
              slide.addShape("rect", {
                rotate,
                fill: { color: getColor(fill) },
                ...baseInfo,
              });
              // 判断有没有文本节点

              if (textNode?.attrs.text) {
                // 识别字体
                const { fontSize, fill, text } = textNode.attrs;
                //  创建文本节点
                slide.addText(text, {
                  valign: "middle",
                  align: "center",
                  rotate,
                  ...baseInfo,
                  color: getColor(fill),
                  fontSize,
                });
              }

              break;

            // 表格 addTable
            case "Table":
              slide.addTable(<PptxGenJS.TableRow[]>data, {
                ...baseInfo,
              });
              break;
            // 统计图 addChart
            case "Echart":
              // slide.addChart(pptx.ChartType.line, dataChartAreaLine, {
              //   ...baseInfo,
              // });
              break;
            // 文本 addText
            case "Text":
              slide.addText([{ text: "TEXT", options: { ...baseInfo } }]);
              break;
            // 多媒体 addMedia
            case "Video":
              break;
            // 图片 addImage
            case "Image":
              slide.addImage({ data: group.attrs.source, ...baseInfo });
              break;

            default:
              break;
          }

          console.log(group);
        }
      });
    });

    // 保存为文件
    const selector = ".konva-root-topmenu-filename";
    const filename = <HTMLElement>root.querySelector(selector);
    const outFileName = `UniPPTX_${filename?.innerText}.pptx`;

    pptx
      .writeFile({ fileName: outFileName })
      .then(() => message.success("文件导出成功!"))
      .catch(() => message.error("文件导出失败!"));
  }

  // 另存为 PDF
  public exportPdf() {
    console.log("command exportPdf");
  }

  // 导出为 图片
  public exportImage() {
    console.log("command exportImage");
  }

  // 分享发送
  public share() {
    // 分享文档 需要唯一ID KPID
    const root = this.draw.getRootBox();
    const kpid = root.getAttribute("kpid");

    // kpid 需要经过加密，还需要传递用户信息 谁的文档

    // 创建 分享链接
    const shareUrl = `${window.location.href}?kpid=${kpid}`;
    const message = this.draw.getMessage();

    // 写入剪贴板
    writeClipboard(shareUrl);

    message.success("分享链接已复制到剪贴板，快去分享给小伙伴吧！");
  }

  // 文档加密
  public encrypt() {
    console.log("command encrypt");
  }

  // 打印
  public print() {
    console.log("command print");
  }

  /**
   * 显示帮助 - 快捷键操作/常见问题/意见反馈（通过参数实现显示不同的内容）
   * @param mode "shortcut" | "question" | "feedback"
   */
  public help(mode: "shortcut" | "question" | "feedback") {
    console.log("command help", mode);

    if (mode === "question")
      window.open("https://wf0.github.io/unippt/faq.html", "_blank");
    if (mode === "feedback")
      window.open("https://gitee.com/wfeng0/uni-pptx/issues/new", "_blank");
  }

  /**
   * 保存
   * @returns string[] 返回值是当前所有幻灯片的JSON数据，可进行存储
   */
  public save() {
    const message = this.draw.getMessage();
    const layerData = this.draw.getLayerManager().getLayerData();
    const data = JSON.stringify(layerData);
    message.success("保存成功");
    // 触发saved 事件 - 将 JSON 数据进行保存
    const eventbus = this.draw.getEventBus();
    nextTick(() => {
      if (eventbus.isSubscribe("saved")) eventbus.emit("saved", data);
    });
    console.clear();
    console.log(data);
    return data;
  }

  // 设置主题
  public setTheme() {
    console.log("setTheme");
  }

  // 智能美化
  public beautify() {
    console.log("beautify");
  }

  /**
   * 打开幻灯片模板
   */
  public openSlideTemp() {
    const dialog = this.draw.getDialog();
    dialog.open({
      title: "请选择幻灯片模板",
      width: 780,
      height: 520,
      content: slideTemp,
    });
  }

  /**
   * 历史记录 - 撤销
   */
  public undo() {
    const historyManager = this.draw.getHistoryManager();
    historyManager.undo();
  }

  /**
   * 历史记录 - 重做
   */
  public redo() {
    const historyManager = this.draw.getHistoryManager();
    historyManager.redo();
  }

  /**
   * 复制 - 当前选中的元素
   *  移入幻灯片，可支持复制幻灯片
   */
  public copy() {
    // 判断 stage copySlideFlag
    const stage = this.draw.getStage();
    const layerManager = this.draw.getLayerManager();

    // 如果 存在，则表示当前的复制操作应该是复制幻灯片
    if (stage.attrs.copySlideFlag) {
      const layer = this.draw.getLayer();
      if (!layer) return;
      // 将当前 layer 直接添加到 active 后面
      const cloneLayer = layer.clone();
      return layerManager.setCopyLayerList(cloneLayer);
    }

    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;

    const data = selected.map((g) => g.clone());

    layerManager.setCopyGroupList(data);
    this.draw.render();
  }

  /**
   * 粘贴
   */
  public async paste() {
    const stage = this.draw.getStage();
    const layerManager = this.draw.getLayerManager();

    if (stage.attrs.copySlideFlag) {
      const copyLayer = layerManager.getCopyLayerList()?.clone();
      if (!copyLayer) return;
      // 每次粘贴都应该更新 ID 值
      copyLayer.id(getUniqueId());
      this.draw.clearLayer();
      this.draw.setLayer(copyLayer);
      stage.add(copyLayer);
      layerManager.cacheLayer();
      // 触发缩略图更新
      return this.draw.render();
    }

    const copyGroupList = layerManager.getCopyGroupList();
    const layer = this.draw.getLayer();

    if (!copyGroupList.length || !layer) return;

    const newGroupList = <Konva.Group[]>[];

    // 粘贴时，取消上个元素的选中状态
    this.draw.clearTransformer();
    this.draw.clearLineControl();
    this.draw.clearSelected();

    // 不然 直接将 groupList 的group 修改 ID 及 x y 重新添加到当前 layer 上
    copyGroupList.forEach((group) => {
      const newGroup = group.clone();
      newGroup.setAttrs({
        id: getUniqueId(),
        x: newGroup.x() + 20,
        y: newGroup.y() + 20,
      });

      newGroupList.push(newGroup.clone());

      this.draw.getKonvaGraph().addShape({ shape: newGroup });
      setGroupTransformer(this.draw, newGroup);
    });

    // 实现持续的复制粘贴
    layerManager.setCopyGroupList(newGroupList);
    this.draw.render();
  }

  /**
   * 剪切
   */
  public async cut() {
    const stage = this.draw.getStage();
    const layerManager = this.draw.getLayerManager();
    if (stage.attrs.copySlideFlag) {
      const layer = this.draw.getLayer();
      if (!layer) return;
      const cloneLayer = layer.clone();
      layerManager.setCopyLayerList(cloneLayer);
      // 删除幻灯片
      return layerManager.deleteSlide(layerManager.getCurrentIndex());
    }

    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;

    const data = selected.map((g) => g.clone());

    layerManager.setCopyGroupList(data);

    selected.forEach((g) => g.destroy());

    this.draw.clearTransformer();
    this.draw.clearLineControl();
    this.draw.clearSelected();

    this.draw.render();
  }

  /**
   * 层级设置 - 置顶
   */
  public top() {
    const layer = this.draw.getLayer();
    if (!layer) return;
    const selected = this.draw.getKonvaGraph().getSelected();
    selected.forEach((g) => g.moveToTop());
    this.draw.render();
  }

  /**
   * 层级设置 - 上移一层
   */
  public holdup() {
    const layer = this.draw.getLayer();
    if (!layer) return;
    const selected = this.draw.getKonvaGraph().getSelected();
    selected.forEach((g) => g.moveUp());
    this.draw.render();
  }

  /**
   * 层级设置 - 下移一层
   */
  public putdown() {
    const layer = this.draw.getLayer();
    if (!layer) return;
    const selected = this.draw.getKonvaGraph().getSelected();
    selected.forEach((g) => {
      if (g.zIndex() <= 1) return; // 不能比背景图层底
      g.moveDown();
    });
    this.draw.render();
  }

  /**
   * 层级设置 - 置底
   */
  public bottom() {
    const layer = this.draw.getLayer();
    if (!layer) return;
    const selected = this.draw.getKonvaGraph().getSelected();
    selected.forEach((g) => {
      g.moveToBottom();
      g.moveUp(); // 确保背景图层在最底下，因此，需要上移一层
    });
    this.draw.render();
  }

  /**
   * 对齐方式 - 水平居左
   */
  public horizontleft() {
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;
    selected.forEach((g) => g.setAttrs({ x: 0 }));
    this.draw.render();
  }

  /**
   * 对齐方式 - 水平居中
   */
  public horizoncenter() {
    const stage = this.draw.getStage();
    const { width } = stage.size();
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;
    selected.forEach((g) => g.setAttrs({ x: width / 2 - g.width() / 2 }));
    this.draw.render();
  }

  /**
   * 对齐方式 - 水平居右
   */
  public horizonright() {
    const stage = this.draw.getStage();
    const { width } = stage.size();
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;
    selected.forEach((g) => g.setAttrs({ x: width - g.width() }));
    this.draw.render();
  }

  /**
   * 对齐方式 - 垂直置顶
   */
  public verticaltop() {
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;
    selected.forEach((g) => g.setAttrs({ y: 0 }));
    this.draw.render();
  }

  /**
   * 对齐方式 - 垂直居中
   */
  public verticalcenter() {
    const stage = this.draw.getStage();
    const { height } = stage.size();
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;
    selected.forEach((g) => g.setAttrs({ y: height / 2 - g.height() / 2 }));
    this.draw.render();
  }

  /**
   * 对齐方式 - 垂直置底
   * @returns
   */
  public verticalbottom() {
    const stage = this.draw.getStage();
    const { height } = stage.size();
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length) return;
    selected.forEach((g) => g.setAttrs({ y: height - g.height() }));
    this.draw.render();
  }

  /**
   * 元素动画设置
   */
  public animation() {
    const shapeSettings = this.draw.getShapeSettings();
    shapeSettings.open(2);
  }

  /**
   * 元素属性设置
   */
  public openSetting() {
    const shapeSettings = this.draw.getShapeSettings();
    shapeSettings.open(1);
  }

  /**
   * 更新元素属性
   * @param options 配置参数 width、height、x、y、left、top、fill、stroke...
   */
  public updateShapeOptions(options: IKonva.UpdateShapeOptions) {
    const selected = this.draw.getKonvaGraph().getSelected();
    if (!selected.length || !options) return;

    // 调用 Utils 模块公共更新方法
    updateShapeOptions(selected, options);

    /** 广播属性 */
    this.draw.broadcast({
      type: `${Math.random()}_updateOptions`, // 这里要每一次变化都保存记录
      payload: {
        options,
        layerID: this.draw.getLayer()?.id(),
        shapeID: selected.map((i) => i.id()),
      },
    });

    this.draw.render();
  }

  /**
   * 更新直线属性
   * @param options 配置参数 lineWidth lineColor showPointer
   */
  public updateLineOptions(options: IKonva.UpdateLineOptions) {
    const selected = this.draw.getKonvaGraph().getLineSelected();
    if (!selected.length || !options) return;

    const { lineWidth, lineColor, showPointer } = options;

    selected.forEach((line) => {
      if (lineWidth) line.setAttrs({ strokeWidth: lineWidth });

      if (lineColor) line.setAttrs({ stroke: lineColor, fill: lineColor });

      if (showPointer) {
        line.setAttrs({ pointerLength: 20, pointerWidth: 20 });
      } else if (showPointer === false) {
        line.setAttrs({ pointerLength: 0, pointerWidth: 0 });
      }
    });
  }

  /**
   * 文件重命名
   */
  public rename() {
    const root = this.draw.getRootBox();
    const menutopSelector = ".konva-root-topmenu .konva-root-topmenu-filename";
    const nameInput = <HTMLDivElement>root.querySelector(menutopSelector);
    // 移动光标至末尾
    const range = document.createRange();
    range.selectNodeContents(nameInput);
    range.collapse(false);
    const sel = <Selection>window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    nameInput.focus();
  }

  /**
   * 背景设置
   * @param {payload: IBackgroundLayerOptions} 支持global、grid、gridColor、ruler、bgType、fill、stroke、image
   */
  public setBackground(payload: IBackgroundLayerOptions) {
    const { grid } = payload || {};
    const layerManager = this.draw.getLayerManager();
    // 设置网格线状态
    if (typeof grid !== "undefined") layerManager.setGridLine(grid);
    layerManager.setBackgroundLayer(payload);
  }

  /**
   * 删除幻灯片
   * @param index 幻灯片索引 默认为当前
   */
  public deleteSlide(index?: number) {
    const layerManager = this.draw.getLayerManager();
    // index 可能为 0,因此不能直接  index?xxx:xxx
    const idx = isEmpty(index) ? layerManager.getCurrentIndex() : index! - 1;

    // 获取当前 layer id
    const layer = this.draw.getLayer();
    if (!layer) return;

    const layerId = layer.id();

    layerManager.deleteSlide(idx);

    // 删除幻灯片协同
    this.draw.broadcast({
      type: `${layerId}_deleteSlide`,
      payload: { layerId, idx },
    });
  }

  /**
   * 删除选中的删除图形
   */
  public deleteGraph() {
    const layer = this.draw.getLayer();
    if (!layer) return;
    const konvaGraph = this.draw.getKonvaGraph();
    const selected = konvaGraph.getSelected();
    const lineSelected = konvaGraph.getLineSelected();

    // 广播删除协同
    const deletedIds = [...selected, ...lineSelected].map((n) => n.id());

    this.draw.broadcast({
      type: `${layer.id()}_deleteGraph`,
      payload: { deletedIds, layerID: layer.id() },
    });

    lineSelected.forEach((n) => n.destroy());
    selected.forEach((n) => n.destroy());

    this.draw.clearTransformer();
    this.draw.clearLineControl();
    this.draw.clearSelected();
    this.draw.render();
  }

  /**
   * 上下左右键移动元素
   */
  public moveGraph(e: Event) {
    if (!e || isPreview()) return;
    // 是否处于预览状态

    // 不然就是处于编辑状态
    const konvaGraph = this.draw.getKonvaGraph();
    const groupSelected = konvaGraph.getSelected();
    const lineSelected = konvaGraph.getLineSelected();

    const selected = [...groupSelected, ...lineSelected];

    if (!selected.length) return;

    const { code, ctrlKey } = <KeyboardEvent>e;

    const OFFSET = ctrlKey ? 1 : 5; // 按 ctrl 移动距离为 1

    const ArrowLeft = () => {
      selected.forEach((g) => g.setAttrs({ x: g.x() - OFFSET }));
    };
    function ArrowRight() {
      selected.forEach((g) => g.setAttrs({ x: g.x() + OFFSET }));
    }
    function ArrowUp() {
      selected.forEach((g) => g.setAttrs({ y: g.y() - OFFSET }));
    }
    function ArrowDown() {
      selected.forEach((g) => g.setAttrs({ y: g.y() + OFFSET }));
    }

    const eventMap: IKeyMap<string, VoidFun> = {
      ArrowRight,
      ArrowUp,
      ArrowLeft,
      ArrowDown,
    };

    if (eventMap[code]) eventMap[code]();

    if (lineSelected.length) {
      this.draw.clearLineControl();
      lineSelected.forEach((line) => setLineSelected(line, this.draw));
    }

    this.draw.render();

    // 移动元素协同-未实现协同功能
  }

  /**
   * 全选
   */
  public selectAll() {
    const konvaGraph = this.draw.getKonvaGraph();
    const allGroup = konvaGraph.getBaseGraph();
    const allLine = konvaGraph.getBaseLine();

    allGroup.forEach((g) => {
      // 1. 添加 selected 属性
      g.setAttrs({ selected: true });
      // 2. 添加 形变节点
      setGroupTransformer(this.draw, g);
    });

    allLine.forEach((line) => setLineSelected(line, this.draw));
  }

  /**
   * 打开消息中心
   */
  public openMessage() {
    console.log("openMessage");
  }

  /**
   * 插入图片
   */
  public insertImage() {
    const konvaGraph = this.draw.getKonvaGraph();

    const input = document.createElement("input");
    input.type = "file";
    input.accept = "image/*";
    input.style.display = "none";

    input.onchange = async (e: Event) => {
      const source = (<HTMLInputElement>e.target).files![0];
      const image = await konvaGraph.Image({ source });
      // 结束后
      konvaGraph.addShape({ shape: image });

      /** 插入图片协同 */
      this.draw.broadcast({
        type: `${image.id()}_insert`,
        payload: {
          id: image.id(),
          layerID: this.draw.getLayer()?.id(),
          source: image.attrs.source,
          insertShape: "image",
        },
      });
      input.value = "";
      input.remove();
      setStageCursor(this.draw, "default");
    };

    input.click();
  }

  /**
   * 插入音频
   */
  public insertMedia() {
    const konvaGraph = this.draw.getKonvaGraph();

    const input = document.createElement("input");
    input.type = "file";
    input.accept = "audio/*,mp4/*,gif/*,mp3/*,video/*";
    input.style.display = "none";
    input.onchange = async (e: Event) => {
      const source = (<HTMLInputElement>e.target).files![0];
      const video = await konvaGraph.Video({ image: undefined, source });
      // 结束后
      konvaGraph.addShape({ shape: video });

      // 插入音频-未实现协同功能

      input.value = "";
      input.remove();
      setStageCursor(this.draw, "default");
    };

    input.click();
  }

  /**
   * 插入公式
   */
  public insertLatex() {
    const dialog = this.draw.getDialog();
    const konvaGraph = this.draw.getKonvaGraph();

    dialog.open({
      width: 720,
      height: 440,
      title: "公式编辑",
      content: latexTemp,
      onConfirm: async (source: Blob) => {
        if (!source) return;
        //  将 blob 转成图片
        const latex = await konvaGraph.Image({ source, fill: "#fff" });
        konvaGraph.addShape({ shape: latex });

        // 插入公式协同广播 - 本质就是图片
        this.draw.broadcast({
          type: `${latex.id()}_insert`,
          payload: {
            id: latex.id(),
            layerID: this.draw.getLayer()?.id(),
            source: latex.attrs.source,
            insertShape: "latex",
          },
        });
      },
    });
  }

  /**
   * 插入统计图
   */
  public insertChart() {
    const dialog = this.draw.getDialog();
    const konvaGraph = this.draw.getKonvaGraph();

    dialog.open({
      width: 720,
      height: 440,
      title: "统计图配置",
      content: echartTemp,
      onConfirm: (payload: IKonva.EchartConfig) => {
        if (!payload) return;
        const { echartType, xAxis, series, echartTitle } = payload;
        if (!echartType || !xAxis || !series) return;
        const echartoption = { echartTitle, echartType, xAxis, series };
        konvaGraph.addShape({ shape: konvaGraph.Echart(echartoption) });
      },
    });
  }

  /**
   * 插入表格
   */
  public insertTable() {
    const konvaGraph = this.draw.getKonvaGraph();
    const dialog = this.draw.getDialog();

    dialog.open({
      title: "表格配置",
      content: tableTemp,
      onConfirm: (payload: IKonva.TableConfig) => {
        if (!payload || !payload.data) return;
        const table = konvaGraph.Table({ data: payload.data });
        konvaGraph.addShape({ shape: table });
      },
    });
  }

  /**
   * 插入富文本
   */
  public insertRichText() {
    const dialog = this.draw.getDialog();
    const konvaGraph = this.draw.getKonvaGraph();

    dialog.open({
      title: "富文本编辑",
      content: richTextTemp,
      onConfirm: async (payload: IKonva.RichTextConfig) => {
        const { source, delta } = payload || {};
        if (!source || !delta) return;
        const richText = await konvaGraph.RichText({ source, delta });
        konvaGraph.addShape({ shape: richText });
        console.log("插入富文本协同广播");
        // 插入富文本协同广播 - 本质就是图片
        this.draw.broadcast({
          type: `${richText.id()}_insert`,
          payload: {
            id: richText.id(),
            delta,
            layerID: this.draw.getLayer()?.id(),
            source: richText.attrs.source,
            insertShape: "richText",
          },
        });
      },
    });
  }
}
